/**
 * @license Highcharts JS v6.1.2 (2018-08-31)
 * GridAxis
 *
 * (c) 2016 Lars A. V. Cabrera
 *
 * --- WORK IN PROGRESS ---
 *
 * License: www.highcharts.com/license
 */
'use strict';
(function (factory) {
	if (typeof module === 'object' && module.exports) {
		module.exports = factory;
	} else if (typeof define === 'function' && define.amd) {
		define(function () {
			return factory;
		});
	} else {
		factory(Highcharts);
	}
}(function (Highcharts) {
	(function (H) {
		/**
		* (c) 2016 Highsoft AS
		* Authors: Lars A. V. Cabrera
		*
		* License: www.highcharts.com/license
		*/

		var each = H.each,
		    isObject = H.isObject,
		    pick = H.pick,
		    wrap = H.wrap,
		    Axis = H.Axis,
		    Chart = H.Chart,
		    Tick = H.Tick;


		// Enum for which side the axis is on.
		// Maps to axis.side
		var axisSide = {
		    top: 0,
		    right: 1,
		    bottom: 2,
		    left: 3,
		    0: 'top',
		    1: 'right',
		    2: 'bottom',
		    3: 'left'
		};

		/**
		 * Checks if an axis is the outer axis in its dimension. Since
		 * axes are placed outwards in order, the axis with the highest
		 * index is the outermost axis.
		 *
		 * Example: If there are multiple x-axes at the top of the chart,
		 * this function returns true if the axis supplied is the last
		 * of the x-axes.
		 *
		 * @return true if the axis is the outermost axis in its dimension;
		 *         false if not
		 */
		Axis.prototype.isOuterAxis = function () {
		    var axis = this,
		        thisIndex = -1,
		        isOuter = true;

		    each(this.chart.axes, function (otherAxis, index) {
		        if (otherAxis.side === axis.side) {
		            if (otherAxis === axis) {
		                // Get the index of the axis in question
		                thisIndex = index;

		                // Check thisIndex >= 0 in case thisIndex has
		                // not been found yet
		            } else if (thisIndex >= 0 && index > thisIndex) {
		                // There was an axis on the same side with a
		                // higher index. Exit the loop.
		                isOuter = false;
		                return;
		            }
		        }
		    });
		    // There were either no other axes on the same side,
		    // or the other axes were not farther from the chart
		    return isOuter;
		};

		/**
		 * Shortcut function to Tick.label.getBBox().width.
		 *
		 * @return {number} width - the width of the tick label
		 */
		Tick.prototype.getLabelWidth = function () {
		    return this.label.getBBox().width;
		};

		/**
		 * Get the maximum label length.
		 * This function can be used in states where the axis.maxLabelLength has not
		 * been set.
		 *
		 * @param  {boolean} force - Optional parameter to force a new calculation, even
		 *                           if a value has already been set
		 * @return {number} maxLabelLength - the maximum label length of the axis
		 */
		Axis.prototype.getMaxLabelLength = function (force) {
		    var tickPositions = this.tickPositions,
		        ticks = this.ticks,
		        maxLabelLength = 0;

		    if (!this.maxLabelLength || force) {
		        each(tickPositions, function (tick) {
		            tick = ticks[tick];
		            if (tick && tick.labelLength > maxLabelLength) {
		                maxLabelLength = tick.labelLength;
		            }
		        });
		        this.maxLabelLength = maxLabelLength;
		    }
		    return this.maxLabelLength;
		};

		/**
		 * Adds the axis defined in axis.options.title
		 */
		Axis.prototype.addTitle = function () {
		    var axis = this,
		        renderer = axis.chart.renderer,
		        axisParent = axis.axisParent,
		        horiz = axis.horiz,
		        opposite = axis.opposite,
		        options = axis.options,
		        axisTitleOptions = options.title,
		        hasData,
		        showAxis,
		        textAlign;

		    // For reuse in Axis.render
		    hasData = axis.hasData();
		    axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);

		    // Disregard title generation in original Axis.getOffset()
		    options.title = '';

		    if (!axis.axisTitle) {
		        textAlign = axisTitleOptions.textAlign;
		        if (!textAlign) {
		            textAlign = (horiz ? {
		                low: 'left',
		                middle: 'center',
		                high: 'right'
		            } : {
		                low: opposite ? 'right' : 'left',
		                middle: 'center',
		                high: opposite ? 'left' : 'right'
		            })[axisTitleOptions.align];
		        }
		        axis.axisTitle = renderer.text(
		            axisTitleOptions.text,
		            0,
		            0,
		            axisTitleOptions.useHTML
		        )
		        .attr({
		            zIndex: 7,
		            rotation: axisTitleOptions.rotation || 0,
		            align: textAlign
		        })
		        .addClass('highcharts-axis-title')
        
		        .css(axisTitleOptions.style)
        
		        // Add to axisParent instead of axisGroup, to ignore the space
		        // it takes
		        .add(axisParent);
		        axis.axisTitle.isNew = true;
		    }


		    // hide or show the title depending on whether showEmpty is set
		    axis.axisTitle[showAxis ? 'show' : 'hide'](true);
		};

		/**
		 * Add custom date formats
		 */
		H.dateFormats = {
		    // Week number
		    W: function (timestamp) {
		        var date = new this.Date(timestamp),
		            day = this.get('Day', date) === 0 ? 7 : this.get('Day', date),
		            time = date.getTime(),
		            startOfYear = new Date(this.get('FullYear', date), 0, 1, -6),
		            dayNumber;
		        this.set('Date', date, this.get('Date', date) + 4 - day);
		        dayNumber = Math.floor((time - startOfYear) / 86400000);
		        return 1 + Math.floor(dayNumber / 7);
		    },
		    // First letter of the day of the week, e.g. 'M' for 'Monday'.
		    E: function (timestamp) {
		        return this.dateFormat('%a', timestamp, true).charAt(0);
		    }
		};

		/**
		 * Prevents adding the last tick label if the axis is not a category axis.
		 *
		 * Since numeric labels are normally placed at starts and ends of a range of
		 * value, and this module makes the label point at the value, an "extra" label
		 * would appear.
		 *
		 * @param {function} proceed - the original function
		 */
		wrap(Tick.prototype, 'addLabel', function (proceed) {
		    var axis = this.axis,
		        isCategoryAxis = axis.options.categories !== undefined,
		        tickPositions = axis.tickPositions,
		        lastTick = tickPositions[tickPositions.length - 1],
		        isLastTick = this.pos !== lastTick;

		    if (!axis.options.grid || isCategoryAxis || isLastTick) {
		        proceed.apply(this);
		    }
		});

		/**
		 * Center tick labels vertically and horizontally between ticks
		 *
		 * @param {function} proceed - the original function
		 *
		 * @return {object} object - an object containing x and y positions
		 *                         for the tick
		 */
		wrap(Tick.prototype, 'getLabelPosition', function (proceed, x, y, label) {
		    var retVal = proceed.apply(this, Array.prototype.slice.call(arguments, 1)),
		        axis = this.axis,
		        options = axis.options,
		        tickInterval = options.tickInterval || 1,
		        newX,
		        newPos,
		        axisHeight,
		        fontSize,
		        labelMetrics,
		        lblB,
		        lblH,
		        labelCenter;

		    // Only center tick labels if axis has option grid: true
		    if (options.grid) {
		        fontSize = options.labels.style.fontSize;
		        labelMetrics = axis.chart.renderer.fontMetrics(fontSize, label);
		        lblB = labelMetrics.b;
		        lblH = labelMetrics.h;

		        if (axis.horiz && options.categories === undefined) {
		            // Center x position
		            axisHeight = axis.axisGroup.getBBox().height;
		            newPos = this.pos + tickInterval / 2;
		            retVal.x = axis.translate(newPos) + axis.left;
		            labelCenter = (axisHeight / 2) + (lblH / 2) - Math.abs(lblH - lblB);

		            // Center y position
		            if (axis.side === axisSide.top) {
		                retVal.y = y - labelCenter;
		            } else {
		                retVal.y = y + labelCenter;
		            }
		        } else {
		            // Center y position
		            if (options.categories === undefined) {
		                newPos = this.pos + (tickInterval / 2);
		                retVal.y = axis.translate(newPos) + axis.top + (lblB / 2);
		            }

		            // Center x position
		            newX = (this.getLabelWidth() / 2) - (axis.maxLabelLength / 2);
		            if (axis.side === axisSide.left) {
		                retVal.x += newX;
		            } else {
		                retVal.x -= newX;
		            }
		        }
		    }
		    return retVal;
		});


		/**
		 * Draw vertical ticks extra long to create cell floors and roofs.
		 * Overrides the tickLength for vertical axes.
		 *
		 * @param {function} proceed - the original function
		 * @returns {array} retVal -
		 */
		wrap(Axis.prototype, 'tickSize', function (proceed) {
		    var axis = this,
		        retVal = proceed.apply(axis, Array.prototype.slice.call(arguments, 1)),
		        labelPadding,
		        distance;

		    if (axis.options.grid && !axis.horiz) {
		        labelPadding = (Math.abs(axis.defaultLeftAxisOptions.labels.x) * 2);
		        if (!axis.maxLabelLength) {
		            axis.maxLabelLength = axis.getMaxLabelLength();
		        }
		        distance = axis.maxLabelLength + labelPadding;

		        retVal[0] = distance;
		    }
		    return retVal;
		});

		/**
		 * Disregards space required by axisTitle, by adding axisTitle to axisParent
		 * instead of axisGroup, and disregarding margins and offsets related to
		 * axisTitle.
		 *
		 * @param {function} proceed - the original function
		 */
		wrap(Axis.prototype, 'getOffset', function (proceed) {
		    var axis = this,
		        axisOffset = axis.chart.axisOffset,
		        side = axis.side,
		        axisHeight,
		        tickSize,
		        options = axis.options,
		        axisTitleOptions = options.title,
		        addTitle = axisTitleOptions &&
		                axisTitleOptions.text &&
		                axisTitleOptions.enabled !== false;

		    if (axis.options.grid && isObject(axis.options.title)) {

		        tickSize = axis.tickSize('tick')[0];
		        if (axisOffset[side] && tickSize) {
		            axisHeight = axisOffset[side] + tickSize;
		        }

		        if (addTitle) {
		            // Use the custom addTitle() to add it, while preventing making room
		            // for it
		            axis.addTitle();
		        }

		        proceed.apply(axis, Array.prototype.slice.call(arguments, 1));

		        axisOffset[side] = pick(axisHeight, axisOffset[side]);


		        // Put axis options back after original Axis.getOffset() has been called
		        options.title = axisTitleOptions;

		    } else {
		        proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
		    }
		});

		/**
		 * Prevents rotation of labels when squished, as rotating them would not
		 * help.
		 *
		 * @param {function} proceed - the original function
		 */
		wrap(Axis.prototype, 'renderUnsquish', function (proceed) {
		    if (this.options.grid) {
		        this.labelRotation = 0;
		        this.options.labels.rotation = 0;
		    }
		    proceed.apply(this);
		});

		/**
		 * Places leftmost tick at the start of the axis, to create a left wall.
		 *
		 * @param {function} proceed - the original function
		 */
		wrap(Axis.prototype, 'setOptions', function (proceed, userOptions) {
		    var axis = this;
		    if (userOptions.grid && axis.horiz) {
		        userOptions.startOnTick = true;
		        userOptions.minPadding = 0;
		        userOptions.endOnTick = true;
		    }
		    proceed.apply(this, Array.prototype.slice.call(arguments, 1));
		});

		/**
		 * Draw an extra line on the far side of the the axisLine,
		 * creating cell roofs of a grid.
		 *
		 * @param {function} proceed - the original function
		 */
		wrap(Axis.prototype, 'render', function (proceed) {
		    var axis = this,
		        options = axis.options,
		        labelPadding,
		        distance,
		        lineWidth,
		        linePath,
		        yStartIndex,
		        yEndIndex,
		        xStartIndex,
		        xEndIndex,
		        renderer = axis.chart.renderer,
		        axisGroupBox;

		    if (options.grid) {
		        labelPadding = (Math.abs(axis.defaultLeftAxisOptions.labels.x) * 2);
		        distance = axis.maxLabelLength + labelPadding;
		        lineWidth = options.lineWidth;

		        // Remove right wall before rendering
		        if (axis.rightWall) {
		            axis.rightWall.destroy();
		        }

		        // Call original Axis.render() to obtain axis.axisLine and
		        // axis.axisGroup
		        proceed.apply(axis);

		        axisGroupBox = axis.axisGroup.getBBox();

		        // Add right wall on horizontal axes
		        if (axis.horiz) {
		            axis.rightWall = renderer.path([
		                'M',
		                axisGroupBox.x + axis.width + 1, // account for left wall
		                axisGroupBox.y,
		                'L',
		                axisGroupBox.x + axis.width + 1, // account for left wall
		                axisGroupBox.y + axisGroupBox.height
		            ])
		            .attr({
		                stroke: options.tickColor || '#ccd6eb',
		                'stroke-width': options.tickWidth || 1,
		                zIndex: 7,
		                class: 'grid-wall'
		            })
		            .add(axis.axisGroup);
		        }

		        if (axis.isOuterAxis() && axis.axisLine) {
		            if (axis.horiz) {
		                // -1 to avoid adding distance each time the chart updates
		                distance = axisGroupBox.height - 1;
		            }

		            if (lineWidth) {
		                linePath = axis.getLinePath(lineWidth);
		                xStartIndex = linePath.indexOf('M') + 1;
		                xEndIndex = linePath.indexOf('L') + 1;
		                yStartIndex = linePath.indexOf('M') + 2;
		                yEndIndex = linePath.indexOf('L') + 2;

		                // Negate distance if top or left axis
		                if (axis.side === axisSide.top || axis.side === axisSide.left) {
		                    distance = -distance;
		                }

		                // If axis is horizontal, reposition line path vertically
		                if (axis.horiz) {
		                    linePath[yStartIndex] = linePath[yStartIndex] + distance;
		                    linePath[yEndIndex] = linePath[yEndIndex] + distance;
		                } else {
		                    // If axis is vertical, reposition line path horizontally
		                    linePath[xStartIndex] = linePath[xStartIndex] + distance;
		                    linePath[xEndIndex] = linePath[xEndIndex] + distance;
		                }

		                if (!axis.axisLineExtra) {
		                    axis.axisLineExtra = renderer.path(linePath)
		                        .attr({
		                            stroke: options.lineColor,
		                            'stroke-width': lineWidth,
		                            zIndex: 7
		                        })
		                        .add(axis.axisGroup);
		                } else {
		                    axis.axisLineExtra.animate({
		                        d: linePath
		                    });
		                }

		                // show or hide the line depending on options.showEmpty
		                axis.axisLine[axis.showAxis ? 'show' : 'hide'](true);
		            }
		        }
		    } else {
		        proceed.apply(axis);
		    }
		});

		/**
		 * Wraps chart rendering with the following customizations:
		 * 1. Prohibit timespans of multitudes of a time unit
		 * 2. Draw cell walls on vertical axes
		 *
		 * @param {function} proceed - the original function
		 */
		wrap(Chart.prototype, 'render', function (proceed) {
		    // 25 is optimal height for default fontSize (11px)
		    // 25 / 11 ≈ 2.28
		    var fontSizeToCellHeightRatio = 25 / 11,
		        fontMetrics,
		        fontSize;

		    each(this.axes, function (axis) {
		        var options = axis.options;
		        if (options.grid) {
		            fontSize = options.labels.style.fontSize;
		            fontMetrics = axis.chart.renderer.fontMetrics(fontSize);

		            // Prohibit timespans of multitudes of a time unit,
		            // e.g. two days, three weeks, etc.
		            if (options.type === 'datetime') {
		                options.units = [
		                    ['millisecond', [1]],
		                    ['second', [1]],
		                    ['minute', [1]],
		                    ['hour', [1]],
		                    ['day', [1]],
		                    ['week', [1]],
		                    ['month', [1]],
		                    ['year', null]
		                ];
		            }

		            // Make tick marks taller, creating cell walls of a grid.
		            // Use cellHeight axis option if set
		            if (axis.horiz) {
		                options.tickLength = options.cellHeight ||
		                        fontMetrics.h * fontSizeToCellHeightRatio;
		            } else {
		                options.tickWidth = 1;
		                if (!options.lineWidth) {
		                    options.lineWidth = 1;
		                }
		            }
		        }
		    });

		    // Call original Chart.render()
		    proceed.apply(this);
		});

	}(Highcharts));
	return (function () {


	}());
}));
